/**
 * Copyright (c) 2011 Technical Centre Radio Systems Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ua.tcrs.tabula.util;

import java.util.Iterator;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.NoSuchElementException;

public final class Iterables {
	private Iterables() {}
	
	public static <T> List<T> toList( Iterable<T> iterable ) {
		if ( iterable instanceof List ) {
			return (List<T>)iterable;
		} else {
			ArrayList<T> list = new ArrayList<T>();
			for ( T t: iterable ) {
				list.add( t );
			}
			return list;
		}
	}
	public static <T> Collection<T> toCollection( Iterable<T> iterable ) {
		if ( iterable instanceof Collection ) {
			return (Collection<T>)iterable;
		} else {
			return toList( iterable );
		}
	}
	/**
	 * Returns the first element in {@code iterable} or null if the iterable is empty.
	 */
	public static <T> T first( Iterable<T> iterable ) {
		return first( iterable, null );
	}
	/**
	 * Returns the first element in {@code iterable} or {@code def} if the iterable is empty.
	 */
	public static <T> T first( Iterable<T> iterable, T def ) {
		for ( T t: iterable ) return t;
		return def;
	}
	/**
	 * Returns the last element in {@code iterable} or null if the iterable is empty.
	 */
	public static <T> T last( Iterable<T> iterable ) {
		return last( iterable, null );
	}
	/**
	 * Returns the last element in {@code iterable} or {@code def} if the iterable is empty.
	 */
	public static <T> T last( Iterable<T> iterable, T def ) {
		T result = def;
		for ( T t: iterable ) {
			result = t;
		}
		return result;
	}
	@SuppressWarnings("unchecked")
	public static <T> Iterable<T> concat( Iterable<? extends T> input1, Iterable<? extends T> input2 ) {
		return concat( Arrays.asList( input1, input2 ) );
	}
	@SuppressWarnings("unchecked")
	public static <T> Iterable<T> concat( Iterable<? extends T> input1, Iterable<? extends T> input2, Iterable<? extends T> input3 ) {
		return concat( Arrays.asList( input1, input2, input3 ) );
	}
	public static <T> Iterable<T> concat( final Iterable<? extends Iterable<? extends T>> inputs ) {
		return new Iterable<T>() {
			public Iterator<T> iterator() {
				return new Iterator<T>() {
					final Iterator<? extends Iterable<? extends T>> inputsIterator = inputs.iterator();
					Iterator<? extends T> current = null;
					public boolean hasNext() {
						// first time
						if ( current == null ) {
							if ( inputsIterator.hasNext() ) {
								current = inputsIterator.next().iterator();
							} else {
								return false;
							}
						}
						boolean hasNext;
						while ( !(hasNext = current.hasNext()) && inputsIterator.hasNext() ) {
							current = inputsIterator.next().iterator();
						}
						return hasNext;
					}
					public T next() {
						if ( !hasNext() ) throw new NoSuchElementException();
						return current.next();
					}
					public void remove() {
						throw new UnsupportedOperationException();
					}
				};
			}
		};
	}
	
	public static <F,T> Iterable<T> transform( final Iterable<F> from, final Function<? super F, ? extends T> function ) {
		return new Iterable<T>() {
			public Iterator<T> iterator() {
				return new Iterator<T>() {
					private Iterator<F> fromIterator = from.iterator();
					public boolean hasNext() { return fromIterator.hasNext(); }
					public T next() { return function.apply( fromIterator.next() ); }
					public void remove() { fromIterator.remove(); }
				};
			}
		};
	}
	
	@SuppressWarnings("unchecked")
	public static <T> Iterable<T> filter( final Iterable<?> unfiltered, final Class<T> type ) {
		return (Iterable<T>)filter( unfiltered, Predicates.instanceOf( type ) );
	}
	public static <T> Iterable<T> filter( final Iterable<T> unfiltered, final Predicate<? super T> predicate) {
		return new Iterable<T>() {
			public Iterator<T> iterator() {
				return new FilterIterator<T>( unfiltered.iterator(), predicate );
			}
		};
	}
	public static <T> boolean any( Iterable<T> iterable, Predicate<? super T> predicate ) {
		for ( T t: iterable ) {
			if ( predicate.apply( t) ) {
				return true;
			}
		}
		return false;
	}
	public static boolean any( Iterable<Boolean> iterable ) {
		return any( iterable, Predicates.isTrue() );
	}
	public static <T> boolean all( Iterable<T> iterable, Predicate<? super T> predicate ) {
		for ( T t: iterable ) {
			if ( !predicate.apply( t) ) {
				return false;
			}
		}
		return true;
	}
	public static <T> boolean all( Iterable<Boolean> iterable ) {
		return all( iterable, Predicates.isTrue() );
	}
	private static class FilterIterator<T> implements Iterator<T> {
		private final Iterator<T> unfiltered;
		private final Predicate<? super T> predicate;
		private T next;
		private boolean hasNext = true;
		public FilterIterator( final Iterator<T> unfiltered, final Predicate<? super T> predicate ) {
			this.unfiltered = unfiltered;
			this.predicate = predicate;
		}
		public final boolean hasNext() {
			while ( unfiltered.hasNext()) {
				next = unfiltered.next();
				if ( predicate.apply( next ) ) {
					return hasNext = true;
				}
			}
			return hasNext = false;
		}
		public final T next() {
			if ( !hasNext ) throw new NoSuchElementException();
			return next;
		}
		public final void remove() {
			unfiltered.remove();
		}
	}
}

